/**
 * @purpose Writer tool
 * @description Look for mentions of variables in Liquid syntax across all pages
 *
 * For example,
 *
 *    ---
 *    title: '{% data variables.product.prodname_mobile %} is cool'
 *    shortTitle: '{% data variables.product.prodname_mobile %}'
 *    ---
 *
 *    This also mentions {% data variables.product.prodname_ios %}
 *
 * So in this case, we *know* that `prodname_mobile` and
 * `prodname_ios` inside `data/variables/product.yml` is definitely used.
 * So that variable won't be mentioned as unused.
 *
 */
import fs from 'fs'
import yaml from 'js-yaml'

import { program } from 'commander'

import { loadPages, loadUnversionedTree } from '@/frame/lib/page-data'
import { TokenizationError, TokenKind } from 'liquidjs'
import type { TagToken } from 'liquidjs'

import readFrontmatter from '@/frame/lib/read-frontmatter'
import { getLiquidTokens } from '@/content-linter/lib/helpers/liquid-utils'
import walkFiles from '@/workflows/walk-files'

program
  .description('Finds unused variables in frontmatter, content, and reusables')
  .option('-o, --output-file <path>', 'path to output file', 'stdout')
  .option('--json', 'serialize output in JSON')
  .option('--markdown', 'serialize output as a Markdown comment')
  .parse(process.argv)

type Options = {
  outputFile: string
  json?: boolean
  markdown?: boolean
}
main(program.opts())

async function main(options: Options) {
  const variables = getVariables()
  const pages = await getPages()
  for (const page of pages) {
    try {
      const filePath = page.fullPath
      const fileContent = fs.readFileSync(filePath, 'utf-8')
      const { content, data } = readFrontmatter(fileContent)
      const title = (data && data.title) || ''
      const shortTitle = (data && data.shortTitle) || ''
      const intro = (data && data.intro) || ''
      for (const string of [content, title, shortTitle, intro]) {
        checkString(string, variables)
      }
    } catch (err) {
      if (err instanceof Error && 'code' in err && err.code === 'ENOENT') continue
      throw err
    }
  }
  for (const filePath of getReusableFiles()) {
    const fileContent = fs.readFileSync(filePath, 'utf-8')
    checkString(fileContent, variables)
  }

  const { outputFile, json } = options
  if (!outputFile || outputFile === 'stdout') {
    if (json) {
      console.log(JSON.stringify(Object.fromEntries(variables), null, 2))
    } else {
      console.log(variables)
    }
  } else if (options.markdown) {
    let output = ''
    const keys = Array.from(variables.values()).sort()
    if (keys.length > 0) {
      output += `There are ${variables.size} unused variables.\n\n`
      output += '| Variable | File |\n'
      output += '| --- | --- |\n'
      for (const key of keys) {
        output += `| ${key} | ${variables.get(key)} |\n`
      }
      output += `\nThis comment was generated by the \`find-unused-variables\` script.\n`
    }
    if (outputFile && output) {
      fs.writeFileSync(outputFile, output, 'utf-8')
    } else if (output) {
      console.log(output)
    }
  } else {
    if (json || outputFile.endsWith('.json')) {
      fs.writeFileSync(outputFile, JSON.stringify(Object.fromEntries(variables), null, 2), 'utf-8')
    } else {
      let output = ''
      for (const [key, value] of variables) {
        output += `${key} in ${value}\n`
      }
      fs.writeFileSync(outputFile, output, 'utf-8')
    }
  }
}

function getVariables(): Map<string, string> {
  const variables = new Map<string, string>()
  for (const filePath of walkFiles('data/variables', '.yml')) {
    const dottedPathBase = `variables.${filePath.replace('data/variables/', '').replace('.yml', '').replace(/\//g, '.')}`
    const data = yaml.load(fs.readFileSync(filePath, 'utf-8')) as Record<string, unknown>
    for (const key of Object.keys(data)) {
      const dottedPath = `${dottedPathBase}.${key}`
      variables.set(dottedPath, filePath)
    }
  }
  return variables
}

async function getPages() {
  const unversionedTree = await loadUnversionedTree([])
  const pageList = await loadPages(unversionedTree)
  return pageList
}

function getReusableFiles(root = 'data') {
  const here: string[] = []
  for (const file of fs.readdirSync(root)) {
    const filePath = `${root}/${file}`
    if (fs.statSync(filePath).isDirectory()) {
      here.push(...getReusableFiles(filePath))
    } else if (file.endsWith('.md') && file !== 'README.md') {
      here.push(filePath)
    }
  }
  return here
}

function checkString(string: string, variables: Map<string, string>) {
  try {
    const tokens = getLiquidTokens(string).filter(
      (token): token is TagToken => token.kind === TokenKind.Tag,
    )
    for (const token of tokens) {
      if (token.name === 'data') {
        const { args } = token
        variables.delete(args)
      }
    }
  } catch (err) {
    if (err instanceof TokenizationError) return
    throw err
  }
}
